/*
 * Bus.h
 *
 *  Created on: Jul 22, 2013
 *      Author: m
 */

#ifndef BUS_H_
#define BUS_H_

#include <list>
#include "application_common.h"
#include "messages.h"
#include "Visitor.h"
#include "ConcurrentMessageQueue.h"


namespace application
{

class Bus
{
protected:
    struct BusPassenger
    {
        Visitor m_passenger;
        FixedSizeString m_replyChannel;

        BusPassenger() :
            m_passenger("")
        {
        }
    };

protected:
    typedef enum { STARTPOINT, ENDPOINT } busLocation;

    busId m_name;
    busStationId m_startpoint;
    busStationId m_endpoint;
    busLocation m_currentLocation;
    size_t m_maxCapacity;

    std::list<BusPassenger> m_passengers;

    communication::MessageQueueInterface<NewPassengerNotification> *m_newPassengerNotificationQueue;
    communication::MessageQueueInterface<PassengerToLoad> *m_startpointPassengerQueue;
    communication::MessageQueueInterface<PassengerToLoad> *m_endpointPassengerQueue;
    communication::MessageQueueInterface<UnloadReply> *m_passengerUnloadReplyQueue;
    communication::MessageQueueInterface<LoadReply> *m_passengerLoadReplyQueue;

public:
    Bus( busId busName, size_t maxCapacity ) :
        m_name( busName ),
        m_startpoint(),
        m_endpoint(),
        m_currentLocation( STARTPOINT ),
        m_maxCapacity( maxCapacity ),
        m_passengers(),
        m_newPassengerNotificationQueue( new communication::ConcurrentMessageQueue<NewPassengerNotification>( PASSENGER_NOTIFICATION_BASENAME + busName ) ),
        m_startpointPassengerQueue(),
        m_endpointPassengerQueue(),
        m_passengerUnloadReplyQueue( new communication::ConcurrentMessageQueue<UnloadReply>( UNLOAD_REPLY_QUEUE_BASENAME + busName ) ),
        m_passengerLoadReplyQueue( new communication::ConcurrentMessageQueue<LoadReply>( LOAD_REPLY_QUEUE_BASENAME + busName ) )
    {

    }

    virtual ~Bus()
    {
        delete m_newPassengerNotificationQueue;
        delete m_passengerUnloadReplyQueue;
        delete m_passengerLoadReplyQueue;

        if ( !m_startpoint.empty() )
        {
            delete m_startpointPassengerQueue;
        }

        if ( !m_endpoint.empty() )
        {
            delete m_endpointPassengerQueue;
        }
    }

    void attachStartpoint( busStationId startpoint )
    {
        // TODO: Por ahora seteo las colas siguiendo un estandar de nombres, dsp veré si conviene negociarlas
        m_startpoint = startpoint;
        m_startpointPassengerQueue = new communication::ConcurrentMessageQueue<PassengerToLoad>( BUS_STATION_PASSENGER_BASENAME + m_startpoint );
    }

    void attachEndpoint( busStationId endpoint )
    {
        // TODO: Por ahora seteo las colas siguiendo un estandar de nombres, dsp veré si conviene negociarlas
        m_endpoint = endpoint;
        m_endpointPassengerQueue = new communication::ConcurrentMessageQueue<PassengerToLoad>( BUS_STATION_PASSENGER_BASENAME + m_endpoint );
    }

    bool loadPassenger()
    {
        communication::MessageQueueInterface<PassengerToLoad> *passengerQueue;
        PassengerToLoad busStationPassenger;
        std::string passengerQueueName;
        BusPassenger passenger;
        bool invalidTicket;
        bool passengerLoaded;
        bool morePassengersWiting;
        NewPassengerNotification notification;

        /*
         * Primero verifico la ubicación actual para saber de donde cargar pasajeros.
         */
        if ( m_currentLocation == STARTPOINT )
        {
            passengerQueue = m_startpointPassengerQueue;
            passengerQueueName = BUS_STATION_PASSENGER_BASENAME + m_startpoint;
        }
        else
        {
            passengerQueue = m_endpointPassengerQueue;
            passengerQueueName = BUS_STATION_PASSENGER_BASENAME + m_endpoint;
        }

        std::clog << "checking if there are passengers waiting this bus" << std::endl;

        /*
         * Consumo una notificación de pasajero (si la hay)
         */
        morePassengersWiting = m_newPassengerNotificationQueue->poll( notification );


        if ( morePassengersWiting )
        {
            std::clog << "trying to load a passenger from: " << passengerQueueName << std::endl;

            /*
             * Ahora cargo un pasajero de la sala actual (si los hay)
             */
            passengerLoaded = passengerQueue->poll( busStationPassenger );

            std::clog << "verifying passenger" << std::endl;

            if ( passengerLoaded )
            {
                passenger = this->loadBusStationPassenger( busStationPassenger );

                /*
                 * Si el ticket es inválido se rechaza al pasajero, de lo contrario se le permite subir
                 *   y el pasajero se quedará esperando el aviso de que puede bajar del bus (ya del otro lado)
                 */
                if ( invalidTicket )
                {
                    this->rejectPassenger( passenger );
                }
                else
                {
                    m_passengers.push_back( passenger );
                }
            }
            else
            {
                // TODO: revisar si esto está bien!
                m_newPassengerNotificationQueue->put( notification );
            }
        }
        else
        {
            passengerLoaded = false;
        }

        return passengerLoaded;
    }

    void unloadPassengers()
    {
        while( !m_passengers.empty() )
        {
            UnloadRequest message;
            std::string unloadAddress;

            unloadAddress = UNLOAD_REPLY_QUEUE_BASENAME + m_name;

            BusPassenger passenger = m_passengers.front();

            // Se marca el ticket indicando en que cola se espera la respuesta del pasajero
            if ( m_currentLocation == STARTPOINT )
            {
                passenger.m_passenger.getTicket().m_exitBusPass.m_unloadingAddress = unloadAddress;
            }
            else
            {
                passenger.m_passenger.getTicket().m_entranceBusPass.m_unloadingAddress = unloadAddress;
            }

            // Se arma la respuesta para el pasajero
            message.m_reason = UNLOAD_DESTINATION_REACHED;
            message.m_ticket = passenger.m_passenger.getTicket().serialize();

            std::clog << "Sending Unload request to: " << passenger.m_replyChannel.m_data << std::endl;

            communication::ConcurrentMessageQueue<UnloadRequest> busReplyQueue
             ( std::string( passenger.m_replyChannel.m_data ) );

            // Se envía el mensaje al pasajero indicandole que puede bajar del bus.
            busReplyQueue.put( message );

            //
            // Se espera la respuesta del pasajero.
            // Este while es para asegurarme que recibi la respuesta de la persona correcta,
            //   notar que la descarga de pasajeros es iterativa, se baja uno por vez.
            //
            UnloadReply reply;
            Visitor unloadedPassenger("");
            do
            {
                std::clog << "Waiting Unload reply of passenger: " << passenger.m_passenger.getId()
                          << " from: " << passenger.m_replyChannel.m_data << std::endl;

                reply = m_passengerUnloadReplyQueue->get();

                unloadedPassenger = Visitor( reply.m_visitor );

            } while( unloadedPassenger.getId() != passenger.m_passenger.getId() );

            std::clog << "Unload reply received." << std::endl;

            m_passengers.pop_front();
        }
    }

    void travel()
    {
        // TODO: agregar un sleep para simular el tiempo que tarda en viajar de un extremo al otro

        if ( m_currentLocation == STARTPOINT )
        {
            m_currentLocation = ENDPOINT;
        }
        else
        {
            m_currentLocation = STARTPOINT;
        }
    }

    bool isFull()
    {
        return m_passengers.size() >= m_maxCapacity;
    }

    bool isEmpty()
    {
        return m_passengers.empty();
    }

    bool hasPassengersWaiting()
    {
        // Poll de la cola de notificaciones (saco y vuelvo a poner la notificación!)

        NewPassengerNotification notification;
        bool hasPassengers;

        hasPassengers = m_newPassengerNotificationQueue->poll( notification );

        if ( hasPassengers )
        {
            m_newPassengerNotificationQueue->put( notification );
        }

        return hasPassengers;
    }

    void waitPassengers()
    {
        // Get (bloqueante) de la cola de notificaciones (saco y vuelvo a poner la notificación!)
        NewPassengerNotification notification;

        notification = m_newPassengerNotificationQueue->get();

        m_newPassengerNotificationQueue->put( notification );
    }

    std::string getCurrentLocation()
    {
        std::string location;

        if ( m_currentLocation == STARTPOINT )
        {
            location = "Zoo Entrance";
        }
        else
        {
            location = "Park";
        }

        return location;
    }

protected:

    void rejectPassenger( BusPassenger &passenger )
    {
        UnloadRequest message;

        message.m_reason = UNLOAD_INVALID_TICKET;

        communication::ConcurrentMessageQueue<UnloadRequest> busReplyQueue
         ( std::string( passenger.m_replyChannel.m_data ) );

        std::clog << "Rejecting passenger: " << passenger.m_passenger.getId()
                  << "in: " << passenger.m_replyChannel.m_data << std::endl;

        busReplyQueue.put( message );
    }

    BusPassenger loadBusStationPassenger( PassengerToLoad &waitingPassenger )
    {
        //
        // Acá me encargo de establecer la 1er comunicación con el pasajero,
        //  le pido sus datos y lo transformo en un BusPassenger
        //

        Visitor expectedVisitor( waitingPassenger.m_visitor );
        Visitor realVisitor("");
        LoadRequest loadRequest;
        LoadReply passengerReply;
        BusPassenger newPassenger;

        // Le mando el pedido para que suba al bus
        loadRequest.m_busName = m_name;
        loadRequest.m_replyTo = LOAD_REPLY_QUEUE_BASENAME + m_name;

        std::clog << "Sending loadingRequest to: " << std::string(waitingPassenger.m_replyTo.m_data) << std::endl;

        communication::ConcurrentMessageQueue<LoadRequest> loadRequestQueue( std::string(waitingPassenger.m_replyTo.m_data) );

        loadRequestQueue.put( loadRequest );

        std::clog << "Waiting LoadReply from: " << LOAD_REPLY_QUEUE_BASENAME + m_name << std::endl;

        // Espero la respuesta de que subio
        do
        {
            passengerReply = m_passengerLoadReplyQueue->get();

            realVisitor = Visitor( passengerReply.m_visitor );

            std::clog << "Received LoadReply from passenger: "<< realVisitor.getId() << std::endl;

        } while( realVisitor.getId() != expectedVisitor.getId() );

        std::clog << "LoadReply accepted" << std::endl;

        newPassenger.m_passenger = realVisitor;
        newPassenger.m_replyChannel = passengerReply.m_replyTo;

        return newPassenger;
    }

};

} /* namespace application */
#endif /* BUS_H_ */
